package gu.simplemq;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static gu.simplemq.utils.TypeConversionSupport.stringValueOf;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.net.HostAndPort;

import gu.simplemq.utils.URISupport;

/**
 * @author guyadong
 *
 */
public abstract class MQPropertiesHelper implements Constant {
	private static final ThreadLocal<MQLocationType> locationType = new ThreadLocal<>();
	protected MQConstProvider constProvider = getConstProvider();
	protected MQPropertiesHelper() {}
	
	public abstract MQConstProvider getConstProvider() ;
	
	public MQProperties makeMQProperties(Properties defaults){
		return new MQPropertiesImpl(defaults);
	}
	public MQPropertiesHelper with(MQLocationType type){
		if(type != null){
			locationType.set(type);
		}else{
			locationType.remove();
		}		
		return this;
	}
	/**
	 * 从Map中返回指定的连接参数值(字符串)
	 * @param props 为{@code null}返回{@code null}
	 * @param keys 连接参数名,会以此数组顺序为优先序返回参数值
	 * @return 参数值字符串,没有返回{@code null}
	 */
	@SuppressWarnings("rawtypes")
	private static String connInfoOf(Map props, String... keys){
		if(null == props){
			return null;
		}
        if(keys != null){
        	for(String key : Sets.newLinkedHashSet(Iterables.filter(Arrays.asList(keys),Predicates.notNull()))){
    			String temp = stringValueOf(props,key);
    			if (!Strings.isNullOrEmpty(temp)) {
    				return temp;
    			}
        	}
        }
        return null;
	}
	@SuppressWarnings("rawtypes")
	public String usernameOf(Map props){
		return connInfoOf(props,MQ_USERNAME,constProvider.getMainUserName());
	}
	@SuppressWarnings("rawtypes")
	public String passwordOf(Map props){
		return connInfoOf(props,MQ_PASSWORD,constProvider.getMainPassword());
	}
	@SuppressWarnings("rawtypes")
	public String clientidOf(Map props){
		return connInfoOf(props,MQ_CLIENTID,constProvider.getMainClientID());
	}
	/**
	 * 从Map中返回服务地址(URI)
	 * @param props
	 * @return 服务地址(URI)实例,没有返回{@code null}
	 */
	@SuppressWarnings("rawtypes")
	public URI getLocation(Map props){
		try {
			String temp = firstNonNull(locationType.get(), MQLocationType.DEFAULT)
					.locationStringOf(props, true, constProvider.getOptionalLocationNames());
		    if(temp != null){
		    	return new MQLocation(temp).getUri();
		    }
		    return null;
		} finally {
			locationType.remove();
		}
	}
	/**
	 * 从Map中返回服务地址(URI)字符串
	 * @param props
	 * @return 服务地址(URI)字符串,没有返回{@code null}
	 */
	@SuppressWarnings("rawtypes")
	public String getLocationAsString(Map props){
	    URI uri = getLocation(props);
	    return uri == null ? null : uri.toString();
	}

	/**
	 * 对参数(props)中的服务地址归一化<br>
	 * 优先使用{@code ACON_BROKER_URL}提供的地址,
	 * 如果没有则调用 {@link ActivemqProperties#getLocationAsString(Map, MQLocationType)}获取地址
	 * (为{@code null}则使用默认值{@code DEFAULT_BROKER_BIND_URL}),
	 * 最后将{@code MQTT_serverURI}设置为该值,
	 * 返回时删除props中的其他可代表服务地址的值{@code MQ_URI,MQ_HOST,MQ_PORT,PROVIDER_URL}
	 * @param props
	 * @return always props,确保{@code ACON_BROKER_URL}有定义
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	<M extends Map> M normalizeLocation(M props){
		if(null != props){
			String s = stringValueOf(props, constProvider.getMainLocationName());
			if (Strings.isNullOrEmpty(s)) {
				props.put(constProvider.getMainLocationName(), 
						firstNonNull(getLocationAsString(props), constProvider.getDefaultMQLocation()));
			}
			props.remove(MQ_QUEUE_URI);
			props.remove(MQ_QUEUE_HOST);
			props.remove(MQ_QUEUE_PORT);
			props.remove(MQ_PUBSUB_URI);
			props.remove(MQ_PUBSUB_HOST);
			props.remove(MQ_PUBSUB_PORT);
			if(!MQ_URI.equals(constProvider.getMainLocationName())){
				props.remove(MQ_URI);
			}
			props.remove(MQ_HOST);
			props.remove(MQ_PORT);
			for(String name:constProvider.getOptionalLocationNames()){
				if(!constProvider.getMainLocationName().equals(name)){
					props.remove(name);
				}
			}
		}
		return props;
	}
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private <M extends Map> M normalizeKey(M props,String nativeKey,String generalKey){
		if(null != props){
			if(nativeKey != null && generalKey != null && !generalKey.equals(nativeKey)){
				String s = stringValueOf(props, nativeKey);
				if (Strings.isNullOrEmpty(s)) {
					s = stringValueOf(props,generalKey);
					if(!Strings.isNullOrEmpty(s)){
						props.put(nativeKey, s);
					}
				}
				props.remove(generalKey);
			}
		}
		return props;
	}
	/**
	 * 对参数(props)中的连接参数归一化<br>
	 * @param props
	 * @return always props
	 */
	@SuppressWarnings({ "rawtypes" })
	public <M extends Map> M normalizeProps(M props){
		normalizeLocation(props);
		normalizeKey(props,constProvider.getMainUserName(),MQ_USERNAME);
		normalizeKey(props,constProvider.getMainPassword(),MQ_PASSWORD);
		normalizeKey(props,constProvider.getMainClientID(),MQ_CLIENTID);
		normalizeKey(props,constProvider.getMainTimeout(),MQ_TIMEOUT);

		return props;
	}
	/** 
	 * 根据{@code props}提供的参数创建一组完整的初始化参数<br>
	 * @param props
	 */
	@SuppressWarnings({ "rawtypes" })
	public MQProperties initParameters(Map props){
		// 初始化时复制一份缺省参数
		MQProperties params = asMQProperties(constProvider.getDefaultMQProperties());
		if(null != props){
			// 缺省参数与输入参数合并
			params.putAll(asMQProperties(props));
			URI uri = toNativeSchema(checkNotNull(getLocation(params),"getLocation return null")) ;
			String userinfo = uri.getUserInfo();
			if(userinfo != null && userinfo.indexOf(':') >= 0){
				// 如果URI中以${username}:${password}格式定义了用户名和密码,则优先使用之
				String[] u = userinfo.split(":");
				if(!Strings.isNullOrEmpty(u[0])){
					String keyUsername = firstNonNull(constProvider.getMainUserName(), MQ_USERNAME);
					params.setProperty(keyUsername, u[0]);
				}
				if(!Strings.isNullOrEmpty(u[1])){
					String keyPassword = firstNonNull(constProvider.getMainPassword(), MQ_PASSWORD);
					params.setProperty(keyPassword, u[1]);
				}
				try {
					// 删除URI中的userinfo信息
					uri = URISupport.changeUserinfo(uri, null);
				} catch (URISyntaxException e) {
					throw new RuntimeException(e);
				}
			}
			params.initURI(uri.toString());
		}
		return normalizeProps(params);
	}
	/**
	 * 将指定的 {@link Map} 对象转为{@link MQProperties}对象
	 * @param props
	 * @return {@link MQProperties}对象
	 */
	@SuppressWarnings("rawtypes")
	public MQProperties asMQProperties(Map props){
		if(props instanceof MQProperties){
			return (MQProperties)props;
		}
		return new MQPropertiesImpl().init(props);
	}
	/**
	 * 将类型为{@link Properties}的参数转换为{@code HashMap<String,String>},
	 * key为枚举变量名
	 * @param props
	 * @return HashMap<String,String>实例，props为{@code null}时返回空实例
	 */
	public HashMap<String,String> asMqParameters2(Properties props){
		if(props == null){
			props = new Properties();
		}
		HashMap<String,String> map = Maps.newHashMap();		
		for(Enumeration<?> itor = props.propertyNames();itor.hasMoreElements();){
			String key = (String) itor.nextElement();
			map.put(key, props.get(key).toString());
		}
		return map;
	}
	/**
	 * 将指定的 {@link Map} 对象转为{@link EnumMap}
	 * @param keyType 枚举类型 
	 * @param props 
	 * @param removeIfSet 是否从props删除枚举类型的key
	 * @param strippedPrefix 需要剥离key的前缀
	 * @return {@link EnumMap}对象
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public <E extends Enum<E>> 
	EnumMap<E, Object> asEnumMap(Class<E> keyType, Map props , boolean removeIfSet, String strippedPrefix){
		EnumMap<E, Object> map = new EnumMap<>(keyType);
		if(props != null){
			for(Iterator itor = props.keySet().iterator();itor.hasNext();){
				try {
					Object key = itor.next();
					Object value = props.get(key);
					if(keyType.isInstance(key)){
						map.put((E) key, value);
					}else{
						String name = key.toString();
						if (strippedPrefix != null && name.startsWith(strippedPrefix)) {
							name = name.substring(strippedPrefix.length());
						}
						E ekey = Enum.valueOf(keyType, name);
						map.put(ekey, value);
					}
					if(removeIfSet){
						itor.remove();
					}
				} catch (Throwable e) {
				}
			}
		}
		return map;
		
	}
	@SuppressWarnings("rawtypes")
	public HostAndPort getHostAndPort(Map parameters){
		checkArgument(null != parameters,"parameters is null");
		URI uri = getLocation(parameters);
		checkArgument(uri != null, "NOT DEFINED LOCATION FOR MQLocationType:%s",locationType.get());
		return HostAndPort.fromParts(uri.getHost(), uri.getPort());
	}

	/**
	 * 根据连接参数创建一个{@link URI}对象
	 * @param parameters
	 */
	@SuppressWarnings("rawtypes")
	public URI getCanonicalURI(Map parameters){
		MQProperties props = initParameters(parameters);
		return new MQLocation(props.getProperty(constProvider.getMainLocationName())).getUri();	
	}
	/**
	 * 根据连接参数创建一个{@link URI}对象
	 * @param parameters
	 */
	@SuppressWarnings("rawtypes")
	public URI getLocationlURI(Map parameters){
		MQProperties props = initParameters(parameters);
		return new MQLocation(props.getProperty(constProvider.getMainLocationName())).asLocation();	
	}
	/**
	 * 将指定的 {@link URI} 对象的外部 scheme 转换为对应实现的内部 scheme
	 * @param uri 为 {@code null}时抛出 {@link IllegalArgumentException}
	 * @return {@link URI}对象
	 * @since 2.4.0
	 */
	private URI toNativeSchema(URI uri) {
		String scheme = uri.getScheme();
		try {
			Map<String, String> map = constProvider.getNativeSchemeMap();
			if(map.containsKey(scheme)) {
				return URISupport.changeScheme(uri, map.get(scheme));
			} else if(map.containsValue(scheme)) {
				return uri;
			} else {
				throw new IllegalArgumentException("UNSUPPORTED scheme : " + scheme);
			}
		} catch (URISyntaxException e) {
			throw new RuntimeException(e);
		}
	}
	protected class MQLocation extends BaseMQURI{
		public MQLocation(String uri) {
			super(uri);
		}

		public MQLocation(URI uri) {
			super(uri);
		}

		@Override
		protected MQConstProvider getConstProvider() {
			return constProvider;
		}
		
	}
	@SuppressWarnings("serial")
	protected class MQPropertiesImpl extends MQProperties{

		MQPropertiesImpl() {
			super();
		}

		MQPropertiesImpl(Properties defaults) {
			super(defaults);
		}

		@Override
		public MQPropertiesHelper getPropertiesHelper() {
			return MQPropertiesHelper.this;
		}
		
	}
}
